/*
* @overview: 
* @Author: wgf 
* @Date: 2024-04-25 10:18:29 
*/

import 'dart:async';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:rc_widget/rc_widget.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';

class AppImageProvider extends ImageProvider<AppImageProvider> {
  static final DefaultCacheManager cacheManager = DefaultCacheManager();

  const AppImageProvider({
    required this.url,
    required this.cache,
    this.scale = 1,
  });

  final String url;
  final bool cache;
  final double scale;

  @override
  ImageStreamCompleter loadBuffer(AppImageProvider key, decode) {
    final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();

    return MultiFrameImageStreamCompleter(
      scale: key.scale,
      debugLabel: key.url,
      chunkEvents: chunkEvents.stream,
      codec: _loadAsync(key, decode, chunkEvents),
      informationCollector: () => <DiagnosticsNode>[
        DiagnosticsProperty<ImageProvider>('Image provider', this),
        DiagnosticsProperty<AppImageProvider>('Image key', key),
      ],
    );
  }

  Future<ui.Codec> _loadAsync(
    AppImageProvider key,
    decode,
    StreamController<ImageChunkEvent> chunkEvents,
  ) async {
    try {
      late final Uint8List bytes;

      if (url.isEmpty) {
        throw 'url.isEmpty';
      } else if (cache) {
        bytes = await getFileFromCache(chunkEvents);
      } else {
        bytes = await getFileFromNetwork(false, chunkEvents);
      }

      final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);

      return decode(buffer);
    } catch (e) {
      scheduleMicrotask(() => PaintingBinding.instance.imageCache.evict(key));
      rethrow;
    } finally {
      chunkEvents.close();
    }
  }

  Future<Uint8List> getFileFromCache(StreamController<ImageChunkEvent> chunkEvents) async {
    final FileInfo? fileInfo = await cacheManager.getFileFromCache(url);

    if (fileInfo != null) {
      final cacheBytes = await fileInfo.file.readAsBytes();

      return cacheBytes;
    } else {
      final networkbytes = await getFileFromNetwork(true, chunkEvents);

      return networkbytes;
    }
  }

  Future<Uint8List> getFileFromNetwork(bool isCache, StreamController<ImageChunkEvent> chunkEvents) async {
    final bytes = await RcHttp.image(
      url,
      onReceiveProgress: (cumulative, total) {
        chunkEvents.add(
          ImageChunkEvent(
            expectedTotalBytes: total,
            cumulativeBytesLoaded: cumulative,
          ),
        );
      },
    );

    if (bytes.isEmpty) {
      throw 'bytes.isEmpty';
    }

    if (isCache) {
      await cacheManager.putFile(url, bytes);
    }

    return bytes;
  }

  @override
  Future<AppImageProvider> obtainKey(ImageConfiguration configuration) {
    return SynchronousFuture<AppImageProvider>(this);
  }

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is AppImageProvider && other.url == url && other.scale == scale && other.cache == cache;
  }

  @override
  int get hashCode => Object.hash(url, scale, cache);

  @override
  String toString() => '${objectRuntimeType(this, 'AppImageProvider')}("$url", scale: $scale)';
}
